﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace D.WPF.MVVM.Messaging
{

    public class Messenger : IMessenger
    {
        private static readonly object CreationLock = new object();
        private static IMessenger _defaultInstance;
        private readonly object _registerLock = new object();
        private Dictionary<Type, List<Messenger.WeakActionAndToken>> _recipientsOfSubclassesAction;
        private Dictionary<Type, List<Messenger.WeakActionAndToken>> _recipientsStrictAction;
        private readonly SynchronizationContext _context = SynchronizationContext.Current;
        private bool _isCleanupRegistered;

        /// <summary>
        /// Gets the Messenger's default instance, allowing
        /// to register and send messages in a static manner.
        /// </summary>
        public static IMessenger Default
        {
            get
            {
                if (Messenger._defaultInstance == null)
                {
                    bool lockTaken = false;
                    object creationLock = null;
                    try
                    {
                        Monitor.Enter(creationLock = Messenger.CreationLock, ref lockTaken);
                        if (Messenger._defaultInstance == null)
                            Messenger._defaultInstance = (IMessenger)new Messenger();
                    }
                    finally
                    {
                        if (lockTaken)
                            Monitor.Exit(creationLock);
                    }
                }
                return Messenger._defaultInstance;
            }
        }

        public virtual void Register<TMessage>(object recipient, Action<TMessage> action) => this.Register<TMessage>(recipient, (object)null, false, action);


        public virtual void Register<TMessage>(
          object recipient,
          bool receiveDerivedMessagesToo,
          Action<TMessage> action)
        {
            this.Register<TMessage>(recipient, (object)null, receiveDerivedMessagesToo, action);
        }


        public virtual void Register<TMessage>(object recipient, object token, Action<TMessage> action) => this.Register<TMessage>(recipient, token, false, action);


        public virtual void Register<TMessage>(
          object recipient,
          object token,
          bool receiveDerivedMessagesToo,
          Action<TMessage> action)
        {
            bool lockTaken1 = false;
            object registerLock = null;
            try
            {
                Monitor.Enter(registerLock = this._registerLock, ref lockTaken1);
                Type key = typeof(TMessage);
                Dictionary<Type, List<Messenger.WeakActionAndToken>> dictionary1;
                if (receiveDerivedMessagesToo)
                {
                    if (this._recipientsOfSubclassesAction == null)
                        this._recipientsOfSubclassesAction = new Dictionary<Type, List<Messenger.WeakActionAndToken>>();
                    dictionary1 = this._recipientsOfSubclassesAction;
                }
                else
                {
                    if (this._recipientsStrictAction == null)
                        this._recipientsStrictAction = new Dictionary<Type, List<Messenger.WeakActionAndToken>>();
                    dictionary1 = this._recipientsStrictAction;
                }
                bool lockTaken2 = false;
                Dictionary<Type, List<Messenger.WeakActionAndToken>> dictionary2 = null;
                try
                {
                    Monitor.Enter((object)(dictionary2 = dictionary1), ref lockTaken2);
                    List<Messenger.WeakActionAndToken> weakActionAndTokenList;
                    if (!dictionary1.ContainsKey(key))
                    {
                        weakActionAndTokenList = new List<Messenger.WeakActionAndToken>();
                        dictionary1.Add(key, weakActionAndTokenList);
                    }
                    else
                        weakActionAndTokenList = dictionary1[key];
                    WeakAction<TMessage> weakAction = new WeakAction<TMessage>(recipient, action);
                    Messenger.WeakActionAndToken weakActionAndToken = new Messenger.WeakActionAndToken()
                    {
                        Action = (WeakAction)weakAction,
                        Token = token
                    };
                    weakActionAndTokenList.Add(weakActionAndToken);
                }
                finally
                {
                    if (lockTaken2)
                        Monitor.Exit((object)dictionary2);
                }
            }
            finally
            {
                if (lockTaken1)
                    Monitor.Exit(registerLock);
            }
            this.RequestCleanup();
        }

       
        public virtual void Send<TMessage>(TMessage message) => this.SendToTargetOrType<TMessage>(message, (Type)null, (object)null);

        public virtual void Send<TMessage, TTarget>(TMessage message) => this.SendToTargetOrType<TMessage>(message, typeof(TTarget), (object)null);

        
        public virtual void Send<TMessage>(TMessage message, object token) => this.SendToTargetOrType<TMessage>(message, (Type)null, token);

      
        public virtual void Unregister(object recipient)
        {
            Messenger.UnregisterFromLists(recipient, this._recipientsOfSubclassesAction);
            Messenger.UnregisterFromLists(recipient, this._recipientsStrictAction);
        }

      
        public virtual void Unregister<TMessage>(object recipient) => this.Unregister<TMessage>(recipient, (object)null, (Action<TMessage>)null);

       
        public virtual void Unregister<TMessage>(object recipient, object token) => this.Unregister<TMessage>(recipient, token, (Action<TMessage>)null);

       
        public virtual void Unregister<TMessage>(object recipient, Action<TMessage> action) => this.Unregister<TMessage>(recipient, (object)null, action);


        public virtual void Unregister<TMessage>(
          object recipient,
          object token,
          Action<TMessage> action)
        {
            Messenger.UnregisterFromLists<TMessage>(recipient, token, action, this._recipientsStrictAction);
            Messenger.UnregisterFromLists<TMessage>(recipient, token, action, this._recipientsOfSubclassesAction);
            this.RequestCleanup();
        }

        /// <summary>
        /// Provides a way to override the Messenger.Default instance with
        /// a custom instance, for example for unit testing purposes.
        /// </summary>
        /// <param name="newMessenger">The instance that will be used as Messenger.Default.</param>
        public static void OverrideDefault(IMessenger newMessenger) => Messenger._defaultInstance = newMessenger;

        /// <summary>
        /// Sets the Messenger's default (static) instance to null.
        /// </summary>
        public static void Reset() => Messenger._defaultInstance = (IMessenger)null;

        /// <summary>
        /// Provides a non-static access to the static <see cref="M:GalaSoft.MvvmLight.Messaging.Messenger.Reset" /> method.
        /// Sets the Messenger's default (static) instance to null.
        /// </summary>
        public void ResetAll() => Messenger.Reset();

        private static void CleanupList(
          IDictionary<Type, List<Messenger.WeakActionAndToken>> lists)
        {
            if (lists == null)
                return;
            bool lockTaken = false;
            IDictionary<Type, List<Messenger.WeakActionAndToken>> dictionary = null;
            try
            {
                Monitor.Enter((object)(dictionary = lists), ref lockTaken);
                List<Type> typeList = new List<Type>();
                foreach (KeyValuePair<Type, List<Messenger.WeakActionAndToken>> list in (IEnumerable<KeyValuePair<Type, List<Messenger.WeakActionAndToken>>>)lists)
                {
                    foreach (Messenger.WeakActionAndToken weakActionAndToken in list.Value.Where<Messenger.WeakActionAndToken>((Func<Messenger.WeakActionAndToken, bool>)(item => item.Action == null || !item.Action.IsAlive)).ToList<Messenger.WeakActionAndToken>())
                        list.Value.Remove(weakActionAndToken);
                    if (list.Value.Count == 0)
                        typeList.Add(list.Key);
                }
                foreach (Type key in typeList)
                    lists.Remove(key);
            }
            finally
            {
                if (lockTaken)
                    Monitor.Exit((object)dictionary);
            }
        }

        private static void SendToList<TMessage>(
          TMessage message,
          IEnumerable<Messenger.WeakActionAndToken> weakActionsAndTokens,
          Type messageTargetType,
          object token)
        {
            if (weakActionsAndTokens == null)
                return;
            List<Messenger.WeakActionAndToken> list = weakActionsAndTokens.ToList<Messenger.WeakActionAndToken>();
            foreach (Messenger.WeakActionAndToken weakActionAndToken in list.Take<Messenger.WeakActionAndToken>(list.Count<Messenger.WeakActionAndToken>()).ToList<Messenger.WeakActionAndToken>())
            {
                if (weakActionAndToken.Action is IExecuteWithObject action && weakActionAndToken.Action.IsAlive && weakActionAndToken.Action.Target != null && (messageTargetType == null || weakActionAndToken.Action.Target.GetType() == messageTargetType || messageTargetType.GetTypeInfo().IsAssignableFrom(weakActionAndToken.Action.Target.GetType().GetTypeInfo())) && (weakActionAndToken.Token == null && token == null || weakActionAndToken.Token != null && weakActionAndToken.Token.Equals(token)))
                    action.ExecuteWithObject((object)message);
            }
        }

        private static void UnregisterFromLists(
          object recipient,
          Dictionary<Type, List<Messenger.WeakActionAndToken>> lists)
        {
            if (recipient == null || lists == null || lists.Count == 0)
                return;
            bool lockTaken = false;
            Dictionary<Type, List<Messenger.WeakActionAndToken>> dictionary = null;
            try
            {
                Monitor.Enter((object)(dictionary = lists), ref lockTaken);
                foreach (Type key in lists.Keys)
                {
                    foreach (Messenger.WeakActionAndToken weakActionAndToken in lists[key])
                    {
                        IExecuteWithObject action = (IExecuteWithObject)weakActionAndToken.Action;
                        if (action != null && recipient == action.Target)
                            action.MarkForDeletion();
                    }
                }
            }
            finally
            {
                if (lockTaken)
                    Monitor.Exit((object)dictionary);
            }
        }

        private static void UnregisterFromLists<TMessage>(
          object recipient,
          object token,
          Action<TMessage> action,
          Dictionary<Type, List<Messenger.WeakActionAndToken>> lists)
        {
            Type key = typeof(TMessage);
            if (recipient == null || lists == null || (lists.Count == 0 || !lists.ContainsKey(key)))
                return;
            bool lockTaken = false;
            Dictionary<Type, List<Messenger.WeakActionAndToken>> dictionary = null;
            try
            {
                Monitor.Enter((object)(dictionary = lists), ref lockTaken);
                foreach (Messenger.WeakActionAndToken weakActionAndToken in lists[key])
                {
                    if (weakActionAndToken.Action is WeakAction<TMessage> action1 && recipient == action1.Target && (action == null || action.GetMethodInfo().Name == action1.MethodName) && (token == null || token.Equals(weakActionAndToken.Token)))
                        weakActionAndToken.Action.MarkForDeletion();
                }
            }
            finally
            {
                if (lockTaken)
                    Monitor.Exit((object)dictionary);
            }
        }

        /// <summary>
        /// Notifies the Messenger that the lists of recipients should
        /// be scanned and cleaned up.
        /// Since recipients are stored as <see cref="T:System.WeakReference" />,
        /// recipients can be garbage collected even though the Messenger keeps
        /// them in a list. During the cleanup operation, all "dead"
        /// recipients are removed from the lists. Since this operation
        /// can take a moment, it is only executed when the application is
        /// idle. For this reason, a user of the Messenger class should use
        /// <see cref="M:GalaSoft.MvvmLight.Messaging.Messenger.RequestCleanup" /> instead of forcing one with the
        /// <see cref="M:GalaSoft.MvvmLight.Messaging.Messenger.Cleanup" /> method.
        /// </summary>
        public void RequestCleanup()
        {
            if (this._isCleanupRegistered)
                return;
            Action cleanupAction = new Action(this.Cleanup);
            if (this._context != null)
                this._context.Post((SendOrPostCallback)(_ => cleanupAction()), (object)null);
            else
                cleanupAction();
            this._isCleanupRegistered = true;
        }

        /// <summary>
        /// Scans the recipients' lists for "dead" instances and removes them.
        /// Since recipients are stored as <see cref="T:System.WeakReference" />,
        /// recipients can be garbage collected even though the Messenger keeps
        /// them in a list. During the cleanup operation, all "dead"
        /// recipients are removed from the lists. Since this operation
        /// can take a moment, it is only executed when the application is
        /// idle. For this reason, a user of the Messenger class should use
        /// <see cref="M:GalaSoft.MvvmLight.Messaging.Messenger.RequestCleanup" /> instead of forcing one with the
        /// <see cref="M:GalaSoft.MvvmLight.Messaging.Messenger.Cleanup" /> method.
        /// </summary>
        public void Cleanup()
        {
            Messenger.CleanupList((IDictionary<Type, List<Messenger.WeakActionAndToken>>)this._recipientsOfSubclassesAction);
            Messenger.CleanupList((IDictionary<Type, List<Messenger.WeakActionAndToken>>)this._recipientsStrictAction);
            this._isCleanupRegistered = false;
        }

        private void SendToTargetOrType<TMessage>(
          TMessage message,
          Type messageTargetType,
          object token)
        {
            Type type1 = typeof(TMessage);
            if (this._recipientsOfSubclassesAction != null)
            {
                foreach (Type type2 in this._recipientsOfSubclassesAction.Keys.Take<Type>(this._recipientsOfSubclassesAction.Count<KeyValuePair<Type, List<Messenger.WeakActionAndToken>>>()).ToList<Type>())
                {
                    List<Messenger.WeakActionAndToken> weakActionAndTokenList = (List<Messenger.WeakActionAndToken>)null;
                    if (type1 == type2 || type1.GetTypeInfo().IsSubclassOf(type2) || type2.GetTypeInfo().IsAssignableFrom(type1.GetTypeInfo()))
                    {
                        bool lockTaken = false;
                        Dictionary<Type, List<Messenger.WeakActionAndToken>> subclassesAction = null;
                        try
                        {
                            Monitor.Enter((object)(subclassesAction = this._recipientsOfSubclassesAction), ref lockTaken);
                            weakActionAndTokenList = this._recipientsOfSubclassesAction[type2].Take<Messenger.WeakActionAndToken>(this._recipientsOfSubclassesAction[type2].Count<Messenger.WeakActionAndToken>()).ToList<Messenger.WeakActionAndToken>();
                        }
                        finally
                        {
                            if (lockTaken)
                                Monitor.Exit((object)subclassesAction);
                        }
                    }
                    Messenger.SendToList<TMessage>(message, (IEnumerable<Messenger.WeakActionAndToken>)weakActionAndTokenList, messageTargetType, token);
                }
            }
            if (this._recipientsStrictAction != null)
            {
                List<Messenger.WeakActionAndToken> weakActionAndTokenList = (List<Messenger.WeakActionAndToken>)null;
                bool lockTaken = false;
                Dictionary<Type, List<Messenger.WeakActionAndToken>> recipientsStrictAction = null;
                try
                {
                    Monitor.Enter((object)(recipientsStrictAction = this._recipientsStrictAction), ref lockTaken);
                    if (this._recipientsStrictAction.ContainsKey(type1))
                        weakActionAndTokenList = this._recipientsStrictAction[type1].Take<Messenger.WeakActionAndToken>(this._recipientsStrictAction[type1].Count<Messenger.WeakActionAndToken>()).ToList<Messenger.WeakActionAndToken>();
                }
                finally
                {
                    if (lockTaken)
                        Monitor.Exit((object)recipientsStrictAction);
                }
                if (weakActionAndTokenList != null)
                    Messenger.SendToList<TMessage>(message, (IEnumerable<Messenger.WeakActionAndToken>)weakActionAndTokenList, messageTargetType, token);
            }
            this.RequestCleanup();
        }

        private struct WeakActionAndToken
        {
            public WeakAction Action;
            public object Token;
        }
    }
}
